package org.linlinjava.litemall.core.util;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.nio.charset.Charset;
import java.util.*;
import java.util.stream.Collectors;

@Component
public class RedisUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(RedisUtils.class);

    public static final String WARN_DESC = "redisTemplate操作失败operate:%s, key:%s, value(field):%s";


    private static RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private void setRedis(RedisTemplate<String, Object> redisTemplate) {
        RedisUtils.redisTemplate = redisTemplate;
    }

    public static List<String> zRange(final String key, final long begin, final long end) {
        List<String> result = Lists.newArrayList();
        try {
            Set<byte[]> set = redisTemplate.execute((RedisCallback<Set<byte[]>>) connection -> {
                Set<byte[]> bytes = connection.zRange(serializeStr(key), begin, end);
                connection.close();
                return bytes;
            });
            if (CollectionUtils.isNotEmpty(set)) {
                result = set.stream().map(String::new).collect(Collectors.toList());
            }

        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("zrange查询缓存异常, key:[{}], e:[{}]", key, e);
        }
        return result;
    }

    public static Boolean setEx(final String key, long milliseconds, final String value) {
        Boolean result = Boolean.FALSE;
        try {
            result = redisTemplate.execute((RedisCallback<Boolean>) connection -> {
                Boolean b = connection.setEx(serializeStr(key), milliseconds, serializeStr(value));
                connection.close();
                return b;
            });
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("set key value 缓存异常, key:[{}], value:[{}], e:[{}]", key, value, e);
        }
        return result;
    }

    public static Long del(final String key) {
        Long result = 0L;
        try {
            result = redisTemplate.execute((RedisCallback<Long>) connection -> {
                Long res = connection.del(serializeStr(key));
                connection.close();
                return res;
            });
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("del key 缓存异常, key:[{}], e:[{}]", key, e);
        }
        return result;
    }

    public static String get(final String key) {
        String result = "";
        try {
            byte[] b = redisTemplate.execute((RedisCallback<byte[]>) connection -> {
                byte[] bytes = connection.get(serializeStr(key));
                connection.close();
                return bytes;
            });
            if (b != null) {
                result = new String(b, "UTF-8");
            }
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("get查询缓存异常, key:[{}], e:[{}]", key, e);
        }
        return result;
    }


    // ==============管道流pipeline操作=======================================

    public static Map<String, String> hGetAll(final String key) {
        Map<byte[], byte[]> cache = Maps.newLinkedHashMap();
        try {
            cache = redisTemplate.execute((RedisCallback<Map<byte[], byte[]>>) connection -> {
                Map<byte[], byte[]> map = connection.hGetAll(serializeStr(key));
                connection.close();
                return map;
            });
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("hgetall查询缓存异常, key:[{}], e:[{}]", key, e);
        }
        return deSerializeMap(cache);
    }

    public static List<Map<String, String>> hGetAllPipeLine(final List<String> keys) {
        try {
            List<Object> result = redisTemplate.executePipelined((RedisCallback<Map<byte[], byte[]>>) connection -> {
                connection.openPipeline();
                for (int i = 0, len = keys.size(); i < len; i++) {
                    byte[] keyByte = serializeStr(keys.get(i));
                    connection.hGetAll(keyByte);
                }
                return null;
            }, redisTemplate.getValueSerializer());

            List<Map<String, String>> maps = Lists.newArrayList();
            for (Object o : result) {
                String s = JSONObject.toJSONString(o);
                Map<String, String> params = JSONObject.parseObject(s, new TypeReference<Map<String, String>>() {
                });
                maps.add(params);
            }
            return maps;
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.info("hGetAll pipeline查询缓存异常 e:[{}]", e);
        }
        return Collections.emptyList();
    }

    public static void hMSet(final String key, final Map<String, String> map) {
        try {
            redisTemplate.execute((RedisCallback<Boolean>) connection -> {
                connection.hMSet(serializeStr(key), serializeMap(map));
                connection.close();
                return null;
            });
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("hmset写入缓存异常, key:[{}], e:[{}]", key, e);
        }
    }

    public static void hMSetPipeLine(final Map<String, Map<String, String>> map) {
        try {
            redisTemplate.executePipelined((RedisCallback<Boolean>) connection -> {
                connection.openPipeline();
                map.forEach((k, v) -> {
                    connection.hMSet(serializeStr(k), serializeMap(v));
                });
                return null;
            }, redisTemplate.getValueSerializer());
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("hmset pipeline写入缓存异常 e:[{}]", e);
        }
    }

    public static Long zRem(final String key, final String field) {
        Long result = null;
        try {
            result = redisTemplate.execute((RedisCallback<Long>) connection -> {
                Long l = connection.zRem(serializeStr(key), serializeStr(field));
                connection.close();
                return l;
            });
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("zRem移除缓存异常, key:[{}], e:[{}]", key, e);
        }
        return result;
    }

    public static void zRemPipeLine(final String key, final List<String> fields) {
        try {
            redisTemplate.executePipelined((RedisCallback<Long>) connection -> {
                connection.openPipeline();
                for (String field : fields) {
                    connection.zRem(serializeStr(key), serializeStr(field));
                }
                return null;
            }, redisTemplate.getValueSerializer());
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("zRem pipeline 移除缓存异常, e:[{}]", e);
        }
    }

    public static Boolean zAdd(final String key, final String field) {
        Boolean result = Boolean.FALSE;
        String unixTime = TimeUtils.getUnixTime(new Date());
        Double timestamp = Double.valueOf(unixTime);
        try {
            result = redisTemplate.execute((RedisCallback<Boolean>) connection -> {
                Boolean b = connection.zAdd(serializeStr(key), timestamp, serializeStr(field));
                connection.close();
                return b;
            });
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("zadd写入缓存异常, key:[{}], e:[{}]", key, e);
        }
        return result;
    }

    public static void zAddPipeLine(final String key, final List<String> fields) {
        String unixTime = TimeUtils.getUnixTime(new Date());
        Double timestamp = Double.valueOf(unixTime);
        try {
            redisTemplate.executePipelined((RedisCallback<Boolean>) connection -> {
                connection.openPipeline();
                for (String field : fields) {
                    connection.zAdd(serializeStr(key), timestamp, serializeStr(field));
                }
                return null;
            }, redisTemplate.getValueSerializer());
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("zadd pipeLine 写入缓存异常, e:[{}]", e);
        }
    }

    public static Long hDel(final String key, final List<String> fields) {
        Long result = null;
        List<byte[]> collect = fields.stream().map(RedisUtils::serializeStr).collect(Collectors.toList());

        try {
            for (byte[] field : collect) {
                result = redisTemplate.execute((RedisCallback<Long>) connection -> {
                    Long l = connection.hDel(serializeStr(key), field);
                    connection.close();
                    return l;
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("hdel删除缓存异常, key:[{}], e:[{}]", key, e);
        }
        return result;
    }

    public static void hDelPipeLine(final Map<String, List<String>> map) {
        try {
            redisTemplate.executePipelined((RedisCallback<Long>) connection -> {
                connection.openPipeline();
                map.forEach((k, v) -> {
                    List<byte[]> collect = v.stream().map(RedisUtils::serializeStr).collect(Collectors.toList());
                    for (byte[] field : collect) {
                        connection.hDel(serializeStr(k), field);
                    }
                });
                return null;
            }, redisTemplate.getValueSerializer());
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("hDel pipeLine删除缓存异常, e:[{}]", e);
        }
    }

    public static void delPipeLine(final List<String> keys) {
        try {
            redisTemplate.executePipelined((RedisCallback<Long>) connection -> {
                connection.openPipeline();
                for (String key : keys) {
                    connection.del(serializeStr(key));
                }
                return null;
            }, redisTemplate.getValueSerializer());
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("del pipeLine删除缓存异常, e:[{}]", e);
        }
    }

    // ----------------原子操作-------------------------
    public static void autoIncr(final String key) {
        RedisAtomicLong redisAtomicLong = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
        redisAtomicLong.getAndIncrement();
    }

    public static long autoGet(final String key) {
        RedisAtomicLong redisAtomicLong = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
        return redisAtomicLong.get();
    }
    // -------------------------------------------------

    // ------------------序列化和反序列化------------------------

    public static byte[] serializeStr(String string) {
        return string.getBytes(Charset.forName("UTF-8"));
    }

    private static Map<byte[], byte[]> serializeMap(Map<String, String> map) {
        Map<byte[], byte[]> result = Maps.newLinkedHashMap();
        map.forEach((key, value) -> {
            if (StringUtils.isNotBlank(key) && StringUtils.isNotBlank(value)) {
                byte[] kb = key.getBytes(Charset.forName("UTF-8"));
                byte[] vb = value.getBytes(Charset.forName("UTF-8"));
                result.putIfAbsent(kb, vb);
            }
        });
        return result;
    }

    private static String deSerializeStr(byte[] b) {
        return new String(b);
    }

    private static Map<String, String> deSerializeMap(Map<byte[], byte[]> map) {
        Map<String, String> result = Maps.newLinkedHashMap();
        if (MapUtils.isEmpty(map)) {
            return Maps.newLinkedHashMap();
        }
        map.forEach((k, v) -> {
            if (k != null) {
                String k1 = new String(k);
                String v1 = new String(v);
                result.put(k1, v1);
            }
        });
        return result;
    }
    // ------------------序列化和反序列化-----------------------

    // ------------------对象属性和值散列----------------------

    public static <T> T getHash(Class<T> clazz, Map<String, String> map, T t) {

        Field[] fields = clazz.getDeclaredFields();
        Class<?> aClass = t.getClass();
        for (Field field : fields) {
            String fieldName = field.getName();
            Object value = "";
            if (map.containsKey(fieldName)) {
                value = map.get(fieldName);
            }

            try {
                Field declaredField = aClass.getDeclaredField(fieldName);
                declaredField.setAccessible(true);
                Class<?> type = declaredField.getType();
                if (type.equals(Date.class) && Objects.nonNull(value)) {
                    value = TimeUtils.parseDateStr((String) value, TimeUtils.DEFAULT_PATTERN);
                } else if (type.equals(Long.class) && Objects.nonNull(value)) {
                    value = Long.valueOf((String) value);
                } else if (type.equals(Byte.class) && Objects.nonNull(value)) {
                    value = Byte.valueOf((String) value);
                } else if (type.equals(BigDecimal.class) && Objects.nonNull(value)) {
                    value = new BigDecimal((String) value);
                }
                declaredField.set(t, value);

            } catch (Exception e) {

            }

        }
        return t;
    }

    public static Map<String, String> hash(Class<?> clazz, Object obj) {
        Map<String, String> map = Maps.newConcurrentMap();
        Field[] fields = clazz.getDeclaredFields();

        for (Field field : fields) {
            String fieldName = field.getName();
            String upperChar = fieldName.substring(0, 1).toUpperCase();
            String anotherStr = fieldName.substring(1);
            String methodName = "get" + upperChar + anotherStr;
            Method method = null;
            try {
                method = clazz.getMethod(methodName);
                method.setAccessible(true);
            } catch (Exception e) {

            }

            Object resultValue = null;
            try {
                resultValue = method.invoke(obj);
            } catch (Exception e) {

            }

            if (field.getType().equals(Date.class) && Objects.nonNull(resultValue)) {
                resultValue = TimeUtils.fromDate((Date) resultValue);
            }

            map.put(fieldName, resultValue != null ? resultValue.toString() : "");
        }

        return map;
    }
    // ------------------对象属性和值散列----------------------
}
